/*
 * Copyright 2006 George Georgovassilis <g.georgovassilis[at]gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gwtwidgets.server.spring.enhancer;

import java.util.Random;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.gwtwidgets.server.spring.GWTSpringController;
import org.gwtwidgets.server.spring.ReflectionUtils;

import com.google.gwt.user.client.rpc.RemoteService;

/**
 * Class enhancer based on the Javassist instrumentation library.
 * 
 * @author g.georgovassilis[at]gmail.com
 * 
 */
public class JavassistEnhancer implements ClassEnhancer {
    private final Log logger = LogFactory.getLog(getClass());

    private void delegateMethod(ConstructionContext context, CtMethod methodOfDelegate)
	    throws Exception {
    if (logger.isDebugEnabled()) {
    	logger.debug("generating delegate method " + methodOfDelegate);
    	logger.debug("Implementing method " + methodOfDelegate);
    }

	CtMethod enhancedMethod = CtNewMethod.copy(methodOfDelegate,
		context.enhancedClass, null);

	int modifiers = enhancedMethod.getModifiers();

	if ((modifiers & Modifier.ABSTRACT) != 0) {
	    modifiers ^= Modifier.ABSTRACT;
	    enhancedMethod.setModifiers(modifiers);
	}

	String token = GWTSpringDelegatingController.__GWTSpringDelegatingController__delegate_name
		+ context.delegateCounter;
	StringBuilder body = new StringBuilder();
	body.append(token);
	StringBuilder arguments = new StringBuilder();

	int size = enhancedMethod.getParameterTypes().length;
	String prefix = "";

	for (int i = 0; i < size; i++) {
	    arguments.append(prefix).append("$" + (i + 1));
	    prefix = ",";
	}

	body.append(".").append(enhancedMethod.getName()).append("(").append(
		arguments).append(");");
	boolean doesReturn = !enhancedMethod.getReturnType().equals(
		CtClass.voidType);

	if (doesReturn)
	    body = new StringBuilder("return ").append(body);
	if (logger.isDebugEnabled())
		logger.debug(body);
	enhancedMethod.setBody(body.toString());
	context.enhancedClass.addMethod(enhancedMethod);
    }

    private void implementInterface(ConstructionContext context, CtClass interfaceClass) throws Exception {
    if (logger.isDebugEnabled())
    	logger.debug("Implementing interface " + interfaceClass);
	context.enhancedClass.addInterface(interfaceClass);
	CtMethod[] declaredMethods = interfaceClass.getDeclaredMethods();

//	for (CtMethod method : declaredMethods)
//	    delegateMethod(context, method);
	for (int i = 0; i < declaredMethods.length; i++) 
		delegateMethod(context, declaredMethods[i]);
	context.delegateCounter++;
    }

    private void implementDelegateSetter(ConstructionContext context,
	    CtClass delegateClass) throws Exception {
    if (logger.isDebugEnabled())
    	logger.debug("implementing delegate setter");
	String token = GWTSpringDelegatingController.__GWTSpringDelegatingController__delegate_name
		+ context.fieldCounter;
    if (logger.isDebugEnabled())
    	logger.debug("added field "+token);
	context.enhancedClass
		.addField(new CtField(delegateClass, token, context.enhancedClass));
	StringBuilder body = new StringBuilder("public void " + token
		+ "_setDelegate(");
	body.append(RemoteService.class.getName()).append(" delegate){");
	body.append(token).append(" = (").append(delegateClass.getName())
		.append(")delegate;}");
    if (logger.isDebugEnabled())
    	logger.debug(body);
	context.enhancedClass.addMethod(CtNewMethod
		.make(body.toString(), context.enhancedClass));
	
	context.setterBody+=token+" = ("+delegateClass.getName()+")delegate;\n";
	context.fieldCounter++;
    }
    
    private void implementGlobalDelegateSetter(ConstructionContext context) throws Exception{
	
	CtField[] fields = context.enhancedClass.getDeclaredFields();
	
    if (logger.isDebugEnabled()) {
    	logger.debug("==================== declared fields:");
//    	for (CtField field:fields){
//    	    logger.debug(field);
//    	}
    	for (int i = 0; i < fields.length; i++) {
    		logger.debug(fields[i]);			
		}
    }
	String method = "public void __GWTSpringDelegatingController__setDelegate("+RemoteService.class.getName()+" delegate){\n";
	method+=context.setterBody;
	method+="};";
    if (logger.isDebugEnabled()) 
    	logger.debug(method);
	context.enhancedClass.addMethod(CtNewMethod.make(method, context.enhancedClass));
    }

    private GWTSpringDelegatingController _createController(
	    final RemoteService delegate, boolean unwrapExceptions)
	    throws Exception {
	
	ConstructionContext context = new ConstructionContext();
	// Extend the GWTSpringController
	context.classpool = ClassPool.getDefault();
	ClassLoader delegateClassLoader = delegate.getClass().getClassLoader();
	context.classpool.insertClassPath(new LoaderClassPath(delegateClassLoader));

	Random randomGenerator = new Random();
	String className = GWTSpringDelegatingController.class.getName()
		+ Math.abs(randomGenerator.nextLong());
	context.enhancedClass = context.classpool.makeClass(className);
	Class delegateClassInterfaces[] = ReflectionUtils
		.getImplementedInterfaces(delegate.getClass());
	context.enhancedClass.setSuperclass(context.classpool
		.get(GWTSpringDelegatingController.class.getName()));

	// Bind interfaces
//	for (Class delegateInterface : delegateClassInterfaces) {
//	    CtClass ctDelegateInterface = context.classpool.get(delegateInterface.getName());
//	    implementDelegateSetter(context, ctDelegateInterface);
//	    implementInterface(context, ctDelegateInterface);
//	}
	for (int i = 0; i < delegateClassInterfaces.length; i++) {
	    CtClass ctDelegateInterface = context.classpool.get(delegateClassInterfaces[i].getName());
	    implementDelegateSetter(context, ctDelegateInterface);
	    implementInterface(context, ctDelegateInterface);
	}

	implementGlobalDelegateSetter(context);
	// implement method delegation
	Class proxiedClass = context.enhancedClass.toClass();

	GWTSpringDelegatingController controller = (GWTSpringDelegatingController) proxiedClass
		.newInstance();
	controller.__GWTSpringDelegatingController__setDelegate(delegate);
	return controller;
    }

    public synchronized GWTSpringController createController(RemoteService delegate,
	    boolean unwrapExceptions) {
	try {
	    return _createController(delegate, unwrapExceptions);
	} catch (RuntimeException e) {
	    throw e;
	} catch (Exception e) {
	    e.printStackTrace();
	    throw new RuntimeException(e);
	}
    }
}
